#!/usr/bin/env python3
import sys
import time
import zlib
import copy
import struct
from enum import Enum
import binascii
import hashlib
import argparse
import math
import zipfile, tempfile
import json
import re
import os

BASH_TIPS = dict(NORMAL='\033[0m',BOLD='\033[1m',DIM='\033[2m',UNDERLINE='\033[4m',
                    DEFAULT='\033[39', RED='\033[31m', YELLOW='\033[33m', GREEN='\033[32m',
                    BG_DEFAULT='\033[49m', BG_WHITE='\033[107m')

ERROR_MSG   = BASH_TIPS['RED']+BASH_TIPS['BOLD']+'[ERROR]'+BASH_TIPS['NORMAL']
WARN_MSG    = BASH_TIPS['YELLOW']+BASH_TIPS['BOLD']+'[WARN]'+BASH_TIPS['NORMAL']
INFO_MSG    = BASH_TIPS['GREEN']+BASH_TIPS['BOLD']+'[INFO]'+BASH_TIPS['NORMAL']

VID_LIST_FOR_AUTO_LOOKUP = "(1A86)|(0403)|(067B)|(10C4)"
#                            WCH    FTDI    PL     CL
timeout = 0.5

MAX_RETRY_TIMES = 10

class TimeoutError(Exception): pass

try:
    import serial
    import serial.tools.list_ports
except ImportError:
    print(ERROR_MSG,'PySerial must be installed, run '+BASH_TIPS['GREEN']+'`pip3 install pyserial`',BASH_TIPS['DEFAULT'])
    sys.exit(1)

# AES is from: https://github.com/ricmoo/pyaes, Copyright by Richard Moore
class AES:
    '''Encapsulates the AES block cipher.
    You generally should not need this. Use the AESModeOfOperation classes
    below instead.'''
    @staticmethod
    def _compact_word(word):
        return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3]

    # Number of rounds by keysize
    number_of_rounds = {16: 10, 24: 12, 32: 14}

    # Round constant words
    rcon = [ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91 ]

    # S-box and Inverse S-box (S is for Substitution)
    S = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 ]
    Si =[ 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d ]

    # Transformations for encryption
    T1 = [ 0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a ]
    T2 = [ 0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616 ]
    T3 = [ 0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16 ]
    T4 = [ 0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c ]

    # Transformations for decryption
    T5 = [ 0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742 ]
    T6 = [ 0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857 ]
    T7 = [ 0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8 ]
    T8 = [ 0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0 ]

    # Transformations for decryption key expansion
    U1 = [ 0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3 ]
    U2 = [ 0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697 ]
    U3 = [ 0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46 ]
    U4 = [ 0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d ]

    def __init__(self, key):

        if len(key) not in (16, 24, 32):
            raise ValueError('Invalid key size')

        rounds = self.number_of_rounds[len(key)]

        # Encryption round keys
        self._Ke = [[0] * 4 for i in range(rounds + 1)]

        # Decryption round keys
        self._Kd = [[0] * 4 for i in range(rounds + 1)]

        round_key_count = (rounds + 1) * 4
        KC = len(key) // 4

        # Convert the key into ints
        tk = [ struct.unpack('>i', key[i:i + 4])[0] for i in range(0, len(key), 4) ]

        # Copy values into round key arrays
        for i in range(0, KC):
            self._Ke[i // 4][i % 4] = tk[i]
            self._Kd[rounds - (i // 4)][i % 4] = tk[i]

        # Key expansion (fips-197 section 5.2)
        rconpointer = 0
        t = KC
        while t < round_key_count:

            tt = tk[KC - 1]
            tk[0] ^= ((self.S[(tt >> 16) & 0xFF] << 24) ^
                      (self.S[(tt >>  8) & 0xFF] << 16) ^
                      (self.S[ tt        & 0xFF] <<  8) ^
                       self.S[(tt >> 24) & 0xFF]        ^
                      (self.rcon[rconpointer] << 24))
            rconpointer += 1

            if KC != 8:
                for i in range(1, KC):
                    tk[i] ^= tk[i - 1]

            # Key expansion for 256-bit keys is "slightly different" (fips-197)
            else:
                for i in range(1, KC // 2):
                    tk[i] ^= tk[i - 1]
                tt = tk[KC // 2 - 1]

                tk[KC // 2] ^= (self.S[ tt        & 0xFF]        ^
                               (self.S[(tt >>  8) & 0xFF] <<  8) ^
                               (self.S[(tt >> 16) & 0xFF] << 16) ^
                               (self.S[(tt >> 24) & 0xFF] << 24))

                for i in range(KC // 2 + 1, KC):
                    tk[i] ^= tk[i - 1]

            # Copy values into round key arrays
            j = 0
            while j < KC and t < round_key_count:
                self._Ke[t // 4][t % 4] = tk[j]
                self._Kd[rounds - (t // 4)][t % 4] = tk[j]
                j += 1
                t += 1

        # Inverse-Cipher-ify the decryption round key (fips-197 section 5.3)
        for r in range(1, rounds):
            for j in range(0, 4):
                tt = self._Kd[r][j]
                self._Kd[r][j] = (self.U1[(tt >> 24) & 0xFF] ^
                                  self.U2[(tt >> 16) & 0xFF] ^
                                  self.U3[(tt >>  8) & 0xFF] ^
                                  self.U4[ tt        & 0xFF])

    def encrypt(self, plaintext):
        'Encrypt a block of plain text using the AES block cipher.'

        if len(plaintext) != 16:
            raise ValueError('wrong block length')

        rounds = len(self._Ke) - 1
        (s1, s2, s3) = [1, 2, 3]
        a = [0, 0, 0, 0]

        # Convert plaintext to (ints ^ key)
        t = [(AES._compact_word(plaintext[4 * i:4 * i + 4]) ^ self._Ke[0][i]) for i in range(0, 4)]

        # Apply round transforms
        for r in range(1, rounds):
            for i in range(0, 4):
                a[i] = (self.T1[(t[ i          ] >> 24) & 0xFF] ^
                        self.T2[(t[(i + s1) % 4] >> 16) & 0xFF] ^
                        self.T3[(t[(i + s2) % 4] >>  8) & 0xFF] ^
                        self.T4[ t[(i + s3) % 4]        & 0xFF] ^
                        self._Ke[r][i])
            t = copy.copy(a)

        # The last round is special
        result = [ ]
        for i in range(0, 4):
            tt = self._Ke[rounds][i]
            result.append((self.S[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
            result.append((self.S[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
            result.append((self.S[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
            result.append((self.S[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)

        return result

    def decrypt(self, ciphertext):
        'Decrypt a block of cipher text using the AES block cipher.'

        if len(ciphertext) != 16:
            raise ValueError('wrong block length')

        rounds = len(self._Kd) - 1
        (s1, s2, s3) = [3, 2, 1]
        a = [0, 0, 0, 0]

        # Convert ciphertext to (ints ^ key)
        t = [(AES._compact_word(ciphertext[4 * i:4 * i + 4]) ^ self._Kd[0][i]) for i in range(0, 4)]

        # Apply round transforms
        for r in range(1, rounds):
            for i in range(0, 4):
                a[i] = (self.T5[(t[ i          ] >> 24) & 0xFF] ^
                        self.T6[(t[(i + s1) % 4] >> 16) & 0xFF] ^
                        self.T7[(t[(i + s2) % 4] >>  8) & 0xFF] ^
                        self.T8[ t[(i + s3) % 4]        & 0xFF] ^
                        self._Kd[r][i])
            t = copy.copy(a)

        # The last round is special
        result = [ ]
        for i in range(0, 4):
            tt = self._Kd[rounds][i]
            result.append((self.Si[(t[ i           ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF)
            result.append((self.Si[(t[(i + s1) % 4] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF)
            result.append((self.Si[(t[(i + s2) % 4] >>  8) & 0xFF] ^ (tt >>  8)) & 0xFF)
            result.append((self.Si[ t[(i + s3) % 4]        & 0xFF] ^  tt       ) & 0xFF)

        return result

class AES_128_CBC:

    def __init__(self, key, iv = None):
        self._aes = AES(key)
        if iv is None:
            self._last_cipherblock = [ 0 ] * 16
        elif len(iv) != 16:
            raise ValueError('initialization vector must be 16 bytes')
        else:
            self._last_cipherblock = iv


    def encrypt(self, plaintext):
        if len(plaintext) != 16:
            raise ValueError('plaintext block must be 16 bytes')

        precipherblock = [ (p ^ l) for (p, l) in zip(plaintext, self._last_cipherblock) ]
        self._last_cipherblock = self._aes.encrypt(precipherblock)

        return b''.join(map(lambda x: x.to_bytes(1, 'little'), self._last_cipherblock))

    def decrypt(self, ciphertext):
        if len(ciphertext) != 16:
            raise ValueError('ciphertext block must be 16 bytes')

        cipherblock = ciphertext
        plaintext = [ (p ^ l) for (p, l) in zip(self._aes.decrypt(cipherblock), self._last_cipherblock) ]
        self._last_cipherblock = cipherblock

        return b''.join(map(lambda x: x.to_bytes(1, 'little'), plaintext))


ISP_PROG = '789cedbc0d5854d7d53fbacff7805fe85106039189a390d8d4a283420231632aa226cd4b93a8499b54c801d104150543d2c6b7e0308ca84930471d0cf4959808896d53d351c7d6a4682b62faf1d67c28499a467480d168024460d40073d7dae70c1f2726b7efff7f9fe7defb7f8acfcf357b9dfdb1f6da7bafbdf63efbec35a498b4ffe1d79f16642424146458007644a29b2544ddc88e2aa87820412584c80c840132f20132071420f34001b20014208b4001b20414209b8002e430a000391c28401e0114208f040a90470105c8a38102e431400157264724282758c8dff28335194773dddb805fc25c30bf88b2ace3e5756c6fc1547ba72a5888927f90c89113181bfb4790770291c78e656ccc5cb2c61ec1c90cd36c4d1c4bacc9b7106bea0c624d98cb5a137fc85a93b3586bea5ad69a50c25b13b7f1d6e497796bea9bbc35e198684d7c57b4269f13ada99da235810983f461903e0cd28741fa11907e04a41f01e94740fa51907e14a41f05e94741fa31907e0ca41f03e9c7c4273011f1896323e2936f89884f9d11119f30775c7ce20fc7c527678d8b4f5d3b2e3ea1647c7ce2b6f1f1c92f8f8f4f7d737c7cc2b1c8f8c47723e393cf45c6a7764642fa89907e22a49f08e92742fa68481f0de9a3217d34a4bf19d2df0ce96f86f43743fa58481f0be963217d6cc1544be295a91189b2c840fb3011ca1281b4db33c6aec1f6cfa8985a103121d1915042b8190ce39851c2703319d631b384e56c0ce7b095705c22c33b124b786e162338669508dc6c4674cc2e11b92446722495485c326372249798b83b9830c71d2561dc9d4cb8e3ce92702e8519e1482919c1a532231da92523b9bb98518ebb4a46717398d18e3925a3b9bb99318ebb4bc648a01f57424984348319eb9a5132569ac98c73cd2c1927d918d9652b91a54466bc2bb164bc348b99e09a5532419acd44ba6697444a498cd99554629692992857724994740733d17547c944e94ee626d79d253749294cb42ba5245a4a65625ca92531d25dcccdaebb4a6e96e630935c734a26497733b1aebb4b62a17f580a8825016810fbc995387ba7db057dcd953ecfe1954892180c72752ec656d4489222b19f47feb72db791587fe527c02772ae99585fc5df8ddaef5af85deb2249d2b5a02c0582b6a7dae07923f01a89ada84d7b5e97ced0b8fb42bfe3f5df122317f983d6d7f4dfc887dfb21441948a3656162248bba5e2a12bb9f3120a4846c495787ba21a7011df66a9bf9d9cda80ed88e311eac2e4161362de4408b7e0e41db2944f52cc12a3d5abf14e37f01d274fde299b1208d631252a8ab1e54a4cb424116b8d9f44bb7a82d63d5ab9583759ea1853e5ec0eaadddd6394ca569ef2412ec7de74b6ca29407c81285d87808a600b4492e26cb02b79adac3a4162d5d3027ba4accd6e130f115fa5d86b7d0dca7855a272a28cdc02017e4f807225694dc7d96bdc5e49549d13c82b2feced5fd3b1fb2bad0e9bbe1baa836a8a207afb4cd79e2d984eeb50691a5607ebab666698dcdb5b45c75e8905fbc41e11bc76f946323fd1ca0f9519eb8972d3361008abbc28f2aa68c77667e5a2a5c417d3d4ffedf5597615ebc2357407ad357522d60f65c032534c07ed4ae187245a3269e56f429d5d20a1f2b15c9401d268799cc43c30cfdd7dd0befdaa9409e5946836176d2ff40d6b2ad839be93b4779cbd0c7d81d1fb028b72693a93a25157d1d2f0f675c409206f93e8884ba394b66d2db46d9948f5a3149e8134a0a73201f474c8ae6443db46829c674436456cb37b9ca7892fea066d8b6d5916096d598b6dd90bb2f45953b7c1fcd0115479d4d32906ec27d8e3b1ac757c273167e3bc72bcb33d2183515ee42364219fb427146f90850ea011da3c2354e0ef3055380abf8b713c3c0e756728df722a4b15ecc0af81df11f03b037eef87df1999aa80fa3a8af197a9902f948df17fe248e289197463bd336082798ba1bff980a84ac51a9f457ec6005fd3a34b443dca9285b6018e5d6badc486eaef96a8aea3689a990116db48e335fe4c6b2b8b96777200da2af63d682b16ec2c0b7636d4661cea8fd623a17815cc814c7b02c9d7ea0f754820cf38924a3599e6047a5126eb66e9ba2ad568f99a02bdd03f18e83b5d5a5f19ec1b56e14df20aff32f6917f0cef3f09c05bf68eb1df607a6b32c4934097d86622da189c4b2206fb1cc13eb7ec778e74cdfe584917c17ca04ecc0df2e1be3d9fb307feeff291854e1a1fcaece1d23a691c79fd84a132f7a9d036d6919da46aa9c0bc42a993d07e46fbdb9be0071c23d6519de49551a88bd8bfc0dc461cb3196c4f46867e8efd08f23f2cf31128d3d5684120be91cefe505b0d6b27bd6e982fd68de64de5dbdd80365bb753e7b06db8f4749f2ae693233b4526c5dc4a6cb94d217b764e8fd7ac8a166a97310e3e0fd934592abe05fada68ebab75d8f738e089d61a970873050bf332b6259be26cb3635d70bc2a850d4085c1719bd92a0e1db738fe71ec0ed8b7a86fb16f540fcb2e0cd183d62e43fa0a8e75aea147b775b1d750ae23622bd85a830dd97448b375ba2c2803ca0369aed23c4ef6186c1d61b1efd2b6e4f5b6c4fe02fde01581b6df6f86f7656cbbddd537eacb909738682777ffd2684321af5d374a37d8e776bfef48ef2472209e281120bf3f1e74778ebc22d131f03707f4472eed1cc63ba98d79d7a7981f1d97a981a3ba1d48d1c7ff5143395f69fee80c0efc4cce9afa4306fc4cc69ab8163016fcd36de0b7dec2a961307e360378117dea08f0a959f716c877cb9623ca4d3c0be5f1badfc6815fc6815fc6825fc6825fc6805fc6805f16f21304da6e816b638eb8aec37cd405e3932755a5309ff25d76b99c8776ab421f9e757847807ec289ec0c23d16218f1174b443587b16a13b471e994b947c2ee606ce1d7896f27df0b762700795fb726cfe5e49744f4b7a13e591c8c1146fed1678cf951f4ffffba30e93b847817a11ea0ce09b708e05f439da1aea963a94f4eed4439f4df30a8e71691d8c23733d123a227576dde7c8b521d86f514a09e02f54fef80fade01f5bd13ea7b27d43705ea9b02f54d1de8a322ad2bf4a5aad23098fb931858bfb068d794e622c1111f4e3c2e17f84d502f298168fd8dd7fb5ba9ded7a0af94f1d057c28836cf94403bfffda2eae6a91e52f8edc416768d1c29df857db94795f6df301f9cff302fabd83924bf0c0269be82349c754a6768beae711c928866371a77a34d3be2f613731984e7cfafd67ca246e2f1877865d538a6433c6b5c09a972a1ede881396ac91819ca5285305dee3141185f5d50de082cafca0cb2c585ca75550e969b5e39bcdc13dbbf5eaeb86368b9a13261de996cddd7331acab90ae58883f572bdc81dd2e64f0c3b1aa517dd4ea00dc20bd4369a0360fb0e415e601b217fc709f1851473d3f03a59a04ed3968c81bef625e43dfaeb7548df3abc8cc62d5a190d9b6e5cc6894da13206f35f8ef95fc6390adae67d55c436dafd1eea4596d2373a664b74aef1d2b1bd0dd798304e67301ea97b60aee17ee7228e3a91417f470d83be156802ffbe878e3f47dd7ca6aac90c3c33515e944c49a568838e331ee701d24eec2baac0762b7f95448fab146d3253259a896c061f7629f81abf6a2236e922cc9175e0abb9982433ca2431ca534de8d78d86b981e5bc615a39f1e037839db2de1a20437d0d18330c8c9981798cdb3b9fc8ebbba1cebb7b64a939e8abf0f7d2b20fc17a269dd619faeb3c6273b51065d219623d0ceba0037e82f5f7b8ba604d2411f457ad87a19d0e34a1aefa75ff6314b5b93847829f87e3ba9d1cbd1ffd20da1789fd0159e091575825a05f88bfc90fdb49f17dc6f9dfbbb43612da273fe5741deb2ea2b674250773a517d6698e7817e309c07c297407e50902272f16385b6b1db1de5e47fd514723f8d18dd00ed021b476f67fa9ee943898833819edc0134db79a97623d5d8cea8e833a811f5ca7b531f51b06da18da16db199eab6083e55290196c32da0eae610794e3a4beb53cc1496c39dd10de0b654a0ccae2683c88e5331e7f1ded67ca5301b437fdb01e1f297707c6803df91b9d83e2c126609b59aea1cdc2e7e18e78901de61ddf26a917e61ab5bde33fdae97c1c23807f7db66fc8f3eb8ec6c5d0a7d3a0be0d7a7d9b209c43e4322751c1aff0f87b20bc1e7e97319e36f8dd789aa0bf1fd20dd7d88a7d8df1e406e07737f13a0f7fae3a8599b219d6c2c08b965cd48e295130073606824a710ff8cc96fe578abbbebd8fa589c4d358477df7e80fb5be7f44eab07b02ddc087f51bb41deac5374ee887b536941308d27553fa76c22d2cd5e58b67b885af12796218c8770df807e07739635b798d448781decc0b39a554d264005986ca615e8c3eb4d08af2c0faec4c527730685bff3ad8c317aea96138ff8ff958f37166051de91f806d3b0e639267b472611d3fbf05d2c3b88a041de6f640b80b7e9731b6e53d302f8ae82b3ac056b472a04f07e81ef445505f0ed027ed03a05b07e853eb039aad91253b51cafc682fa11d632f737b0506fc877761fc34a3ce921cc120c87b0dfa9d08fd10fc17dcaf82f9327186a0d7e7231b9385e3e76e6f39f65f86492a0d06555718a7f5d1d23e4fa99b24e133b19ca5eb08278eb1e269aa0863cb15c6d8a426184bf374fd384f5b130298df9dda1873d9d01f45ff08fc5956dba768fada3e45bbe5681c3e6fb7905b70ae71e3dea0d3f96b8d7734de0b360aea5b7ca52e3de14a2e9fa09595f62e8cff44943924afc7153fd79a08732384659eef439e1c66e9b395baa8ad0179188c4fc7e74118431582a8f3d9d098f56d957a43bc81b8c82f967ab9a412a2d91e9ebc026b256abf4df17d43e382ff89766914fab10eb019980675268b3c5d5fbd62c231187bc4ebd2f48d32515debf95af7d3f461d41712c8c83509c5df0ded31c91219a94c6ac5b5e92858cbdd0e6bba5174ef8661983509e43bb2b47f942faab5bf3dc13e0dd6a920c3b27e7587c0525b7a5a60b5b5c636ea8fa22df5f0078912b37d60ad04fd7dc0a7e4d240667e3cfaa657d146d0b50adae2c497619df2265d43396631849b853e018c33f47d212d5d3b829c8e3a9883c6b5b24a25fa45b04eabe9266b889db5d680ad813942764ad4b6d23daacd507f58bb281b9b58b40d2877955960b03e554dd25cdf4dadbde84b40dee0e71c1da544c13c59d38af66d24eef5a8e2a95198ce1779fabac6df8ffabf7ed7f7a7b994b57f62d5a20039e67bfb9cfa9418bc6bf3ef778ef48df4cf957cab9afac79edf58ec11ff64df2ce2fef5ce9db9c5b5c42a79c92ba217bc2ca00250c02b3c50c02b1c50c02b2c50c02b0c50c02b0428606331ee19a93b9de4f473ea4e81a1ffb3a79f7bc4abee8c275a19651bfdc571d777161f33dffcc29c9dbeb57fbafa0befcd6bf75c98563add39bda5ec723b6996dcb9d803eb3affd0ec4cff434b4cd49e9c3997cbd3e79c4a5d30ad5710a65f9dd63572c536ff6488df4e2cf29e1c871bb4e58ee7941f48a47c3957d7107efa056b6537b956d467bef682b5b63b3cc134d909394fee5b3af6dcebe96d99f3b3e738a7666d2cd6c769cf3ee726654d477d609fcb2971609757056dce3afb86091b8b99348835014764dae435a4e6c1b96327378b2dd34aad510789756203f40658b92602e284706b2a500bf4b429027bcbfc923418a5e1ed1df57eb7997a567d31bdda2f89a4366bbf5c7c6a8bf62b572257532fecc964b3e7325aa9ccd8b816585f8da82aed0e7a4a8fb38b9aa7974fbe205e569da523a12e3f9c56cafc8d963e652a9325982e80ed18519206e5354f769ecfbc277b4f36bb5218a863a31f6bb8fb4aeac2972fcd1d2bb64c6ed6d654755fa67efe5cba754af703aae47c10ea95dcfd00e4fa80298799bf0d72bbbbd3b44298bf177eedee30e585cd7f077e05db9de9a92743b2a2a4ba1ecfc5b5942a8b9ac5cb6f2d987c615a79893fce09a3fd9a5aca3f50bca27a5e5f92724622d599e57f29cd6ca8e7cc3c33aa112cd58856efb6b44518b37353f386ce37d371f666e60dafc5b12267bc358e0fb726809548e449cc496b2afcb2f0a06d5ed7f63cd0f6d953c0e7810ff61e60e14d39f52823fed3a4ac6bdaa4683676ef791893091a37fdcc00f7c21ad23c3de689127f49fa1ce7c6624793c04c5cbbe7ea9f3bdebb50d65bdbf5df97cfb47cdcfc93dec7af2eef7aa263f5e597b7ccdc3cbd745af9bb7faf5e75ecef0e58b538ee08d756ba51d268e796924530ab46326925f732f72aa61153265efc51d9eb693f81fe5811ae48c214413812be7eee92ad4ac18f991f6d3565c6e48dcf50faeb26cf9b77b05af9b8d4faa933aeac9d644c88c9dce35ce4ac75c734b747d8272ae38488239b5fb20b698a3b9ce093f68866f38656475238535b651b516587b9cb6e4d7e89bcdbb5ab863e27a7b2fb964e7df7bbaf397e1f461c07cac9b1b0be0f6bcb94822af271596ae6c8a74be296942d726e742b0fd584633efb5e2adf8ae3c09abc8f3c73ad24bd2d7f7e616da1f84cdbcaf94fd43e213e79fe67f73cbbe75976c3f955739c61d04649234871c9a29279f2bd238e3ee6c552b8b7b19479f36a5dbeb051fd1ce8c694a93e2559944de26865e52f4cf07bc13b27f88c917947f86a76d47355a3c03eba6e269fb98f1cdfce29dd5d44987a84df0bf3a6645ff8f75a5a0bfbe409ad5a1f415dab7e89b7467591bea5cf79b17cf5de4b41e607f2bd5f0547dd577edf23de9179abcba6819dc85c8fa5efc9958b025e39573259a35a48cc65fed417ee23a507e6c634cf3bfe99ebb352a5a08f7ce63265ce293be39c03a99a373c3c6f4feecdbb1f3e1e95f97bd03cd5ee4bb651bf18d0ee8e8ed683716554ae9fef733dd63d328fb6c4582a5b201040d9caeb63f214f062a15d8bfec92a05bf209f95c64109907fd1a3f3525c6e7b4ce6c3c7b165d3ae682d95f1b3473e1ae5fdd8a5f87ec97ce63ad2d8c42643fb60eb1c296b6259f7be52dfbd353d8cd77104747ca49c3069c7c2167baac2a349ce11e5de4f08b6e64f685b42cc1fd47cf2d1db8f977d8ae5e53f3aef7ce911976beea3c76332cf4389eb8f3cb6764f735cef031d4b2e97b52cba7a7f17acb91e60e6098bfa7eb4ebc264676da6987dbef09e67f63cc3fef4fc1359d0cadcefa087bf5e4afaee503e0823a59917eb1d4bc318e5d9ebc461be8d51362d65168165deb8634fcec2adcc14392c6cc447f58365fce8c24f5ab472a66d7e63b35ed6bd7d3f2ebf6c2ceb9e27f73c79a3f23ed2cafbd9575a79ae22e6cfce079c1b5f0a95760e4a8beb5d74b5ac654ff3fd5da1321f6f564bd73330f7b2d3ca957e77063866c5c72aa90fdee226ca970758ddeea47d5d0a360fe500ed65aa527eac72d1cdab4d89a4243eda6526d16633712fc75dbcda31a3a5d96e92ab8a525089fc809035e5f18ae41263b2a3f2c6afe8acdae3aefc62fca5ca5c7c8755e9c756268f82cfc3ccad979bb6e3aa8063feb22d5df1fb4d6a5336787c2e2e94ff68492ba12c8ce62f48c10d694ad8011295bdb6ca26baed4a4643785f9233debd126295df267107c2582dc572e9905309ff607454f686343deee206934782f25ac613bec59d033edc816e91d2b7baf9ededdb2fa9cb23c976ff647784efcdfa77d39556f0b625afb0a35e95ec024847d4a6e3b806149ff3026782c6e9024e915452af8af6c5a063c9e76beb529ba63055ae46e9bbf5e3f39405228959b1bdbdfa338fd442badf9e5d5ae2356399b34ff7533ae7742fc699ec8ec955fd6612e36f38a88a1db1cab9b6c8510e263e7a49bcdd39dfb3f30411e2b943e5ba56de7dfe7eb719ea69b59db93a5e9141ef1197c75f0067f58be8a678bbf07d9bec231cb8f6cae3b9a2e7a95cb2abadb34ae4f0d7b6f8d64f37dc3fa8dfb9e6fbdd776e9ae3e41237314a143f5198aa3e358bcc35b55e577b9288522dc6603e6afa95e0b517ccb9f4bd478703e41039a5653951267231dc9130f022b710f9a7c7834aee4bc423bd64df90aedc1bb855f3661a9f2e56b45ff1f9db950d4be3b6cfd9aaec6e8b82f98fd5c67c7351dfd27d9b15a1dbe291ee6594c26ea2c4a6c4ba9fc41dca279fe2ea46905b7663ab5a7fd556a8542e35ab3bd33965bb18e9f086310f6c97b3c793623fca06ebf0b50e881da5e02a994a6b0be46b4f72d700b7307013e5260656afdd272f71936df395f3277875492eac65cbb8e832583f464692d1e2fd3b65410c463cae4c6c1085f4aa3371764f6413d5e847cfd31c929a9a4d97232e908be48b670ebe3b5f693d41aac443c206ec07d0574e10754923e4d926aef7026782c60900e7296965bd2c405f11a1afac68b8aa2e8967aaca4e483ff4463ca1880d64fcf2ce977c4f36f59b56a779c76747e475bee4d9b4d3aefc781217b562b0f746b4477da6d7ef634a53021fcafe28b2b57deba54aff9e9dd6fd914cd6ef94bf98c89e9dd5cbb15f55b7c5aca8688ff96ce19b334b6f791b476a94d297e41bddd43ceaed6df122670b2c2739675b3dfb5cffac77e6f64973b6f7156dd8ea3cc8cd1e49fa962891674cd886d6faa50faaddd74979c3b1748fd36b7fe32567ee1f762cd9de67deb0d556eab4ebedf9a3bea5caf86eb2c4c9ccc3f6b0098be7bebc7bda762ae9b4c0c3d86234b7c34bff663db099f8d89456e83179e99c33dd7a00ac29b46b65eeda2a8f04a336c74de0d9bc008f391747bdfe078febbafd77bb695e53020f3d57ef4882bec76fee9f5be9dc72ec056be25764ef7c8fb40546f2f64bbef5ae4f5561044367965b3b8f807d6064ff78821a896ad9a34934cb7dbcbae5d78770a7d777c9fb0658a771b8d33bf10fe8d34fdba98c6f20e8953ad26195ff9cb75f10fabc9ec6bde4175b6c520329f1ff225d696f24fb5ccaca565807384e08c4293985575e68eaf77de6ef879541d17af2f6a57d2ef52957502e137b95de46d1e11188e3b74ee2a81718aedec938fe20b0dc1f9c2ce71138c7612707ab601e7d64eeb033687dabe8ba75ffd25eeb81409ff5b0bfcffa56539f757f63d07a20b7df7ad80ccfa5e07aefd072d59e22d27a7d6e3db66545d6cbd2cb82f5adc67edf2590c8efefd9588c722b6d7b5166d5cfc24ac8d5406e7edef74c77bfa7ac916c83b5519fd97142221bcc56d244babd4ec171a23be89430ef6b5ef42771dd5276797a696dcb9f9befef7de0ea92ae1f75fce4f26f9d6197e767b765d6668b2bcf3f73cf4ff7fc94fdd9f927a766c1fa62e7becd335bda3b7ede0b6b96d9a5419c8dd472989546f041f9da89a0723d0937e58ae5afc2827466eacae56056aab94e9845b86679c069cc736331bec5f0ad3ed2ab3cb4d2a68df1f8ba88273daeef40df514c3d445eef0fced961ca11ca175e84be14769128937a585918c12a7f7addf47195a77ce55cda0b0f16ed552f4e84d92005fcd7d2e012e7e4cdeab589c4737125634d4d0ce24ae060bdf2ce4aa1724df59395ed319738ef16e2295b3977c37c618bc795c228ebcf4cf3acbc4473f0389f64d43698475a2b738f094b9cda9cd74ef26134c04ae2b2bc7904739797492f49776e56dc628c239187354e959dfb7d2958d03f0717bda43c2f8df384a58335ee09572590f6c4b571952baaf364b13c286fe69998f6ca4bb254cc2b4aa34915f3d9f75c4adf091293597d2126bbfa32ccd39c72ae919f0bfd8767a3322b7d51d995fea81595973cae6c06ebacdb8bed5a9d9398ea6ce716cfc55cc67a20914119979d7fe660cc8aea8e98bcea2e456a2368a7212f6e717d4c3efce6aaaf7249d0822e9eafec4df3ee010f04d62ffff88157f9e3ca30d41095d3c933a829941be55551de2f1a7959ca67950d8d04e4ba00725d9651da558d642e0f29d898cc987331d931ad312b622ed66e59f93648dd1195570952f45029200ed7895238795e93c4019254f79e032960fdf35ea757b9ff09a93aaf72454886ffb5b2df2b5fe8fd9f951dfb974e2feaee7b17a66df68d163ec367b22404af557dea562afdc4718733a85e7b27f8e75def7a50362a534ba3a8cbc44765469d8bca8e6aa5326535b2b2043eb42430e395edbef1b9dbfd514f565edae35c82b6f5c76f825ea22e46e5457d4e6593a0752481fd086593782ea6737c2184b9f1ddef7af7b926bb36d453f98ebde3fd73f9cb1ecfc58bb497867cb1831e5cd5e30e04ae05adaf495bc596f6840416ecf7c46ea2af9fb7547f7fdb9f628e8656bd98627aa963c75406e2920e5e5928904757ec733f3ab086c715c086a20db803016b80cfbdd47ac25af299b5933be22e2fba707f8bd8a506a4887da5d34b67961f2adfd588b995cf67e683edcd1b9eda31610ac316eec9ffefb245657b56deb332ce793e3b2d0b56affc70398a57f42dc5758e12dd4dfab4dd0f481d92c899fb110d2fb8d0e7c5146b48cdd850cd7feb9c5d46edc0ebd2b3587bcbcff4da539fc5ffb3e1b58715bfc63f1fd7b2af749382fb12d34bc31a5217eef2b53707afe1da5e354bdc3155ed7191d7bf507b24a6fc8bbde9d54761edc5e3da6bc3626d758f6bfb6d5b7634de9eb7ed1d559a2729ce6e12072d7cf42e5cc7a316607d4fd480b70f258fc98335185df5b988162e6fc7b02c110be8f256dc0fdee3c4f43559941f3013a13126c31ac7d3fdf2980f62f298b48fae38ccf84c32cd3a1b5a3f3aebcbb730e9db7c51190f1fefac1fe80fa8911a290f35d27cff508d343e61d4887879f285b89645cde517998558b745ced0de05b6704837775fc0b2af9dd5a51453574c83961ff9741caebb1f14c88647b495a3642a6f1c9987712cdf35ea86dc8ebab9fdd96d0df4f97770a7a3bdf96ca7358a27edcd3fef5ce8d56494ded1f6b57ede833b64d8ef64b7c4cd55e32e8b5dd8ff602e26289b52214608b9b224157ff442651aee9c293f6826871f511635c39cbf279f2d3c9f7dcfca3d2bd3a02ec9d91b9ab8d7a145762671d61a9e8c7c5af9503465ac40a9b76dd9d51833efe6e753f376fd29f561a0d9239f2e998f35eb5bea5802bdf2e66e526d5f9fae3c1e302549768b20d9960688b065d65ffa8a5457e0e75179dd2f54b92506773da39ab155b0ed657712a73c2c11cd9baab66ff32db8707b9eb5660a73f0dda15aa9b909b55252cfcc7742992c9439adcc37a1bba7da7ef07d28d3e9278fa42bdbfd3ca6fcae57f58bb4dd372c16bc8edf3919dfd6b00eee770219b5c5ba7f16931276c7dc92f4b1beea1f3e7adcb7ccd28eebe93f97dd5f465b29bd6660e76143fdbe5d2961afdb3beb99b13806a6954e84762ef74f6f696fdefd8eb633b9ab71b2f39eecf399027a007eec194c1aed154ee673bd9dde08ed3d09bd6b3ad63ca7af137e4df724afdfb176f2d5b8ae451df75f9ede22f64e2b773f82abbc776e6def680fa08fb827a734d7992b9706fa3a2b476d8ddb3ea378c3f899bcf25817c13db5bf773369e7f3ef29dc53c83e737ee52d506e89b6d7f626a6ad75fb2675f77a84623bf2cefe06741cddcd6a31826f7010a30fbcc856bbef1f3dd7b515e5fc57b73faeadd0263c4757237b6b7f95558f79956c45afbcd4af499ffecbeaef2f72a694baec740ced71ed6bef18f3b990b6a35e291626a1b6e25aa695be512a5e6eef5873c97110e6d2323ea8bf97895603ad415b590f895ace250b8cb37c6d9532f124ff7b77f945cecb07773d5ff1c4efdd95aba3be88b824832c956df8fed19ad814a5af7f5e8255ce677e7da4c6bfbc55990cf5a9bfa4b503da9c351dffd112d5c62cd871ce71900fba1f86789b5e1bffdcf36fb8b73ea16c9e10e6385cc658df9ac0580f1f261ea18d6ccd7196ef68fdbd3bea09e56693e85e4cf709c72a66af685e42d79fe3284d391351f945e567c2024d2a9bab87c4f87c134c576509d6b2d9015e6e4a22fa6a36dde36e241139efbe4053ceec195925994995db4c9e97f6b92b15b94c0a56405e30f3b5bfec1516fcba3e6235ae771ef3ca67dc646c3acc8f905b2ebe51e4a245b3be629bb653d5566c950da2333d7a69bcdde6f693989cc75e70a386eafdfcd62f2a3edb7e71fc17a7dffe61ba92d308e90f09c7bc32aeed731b89dc841cbfb8013913340ebeb52c92d6d6abb05e93716d7fb1e1aadc14cf444b8dd223b85e2b6b80765a5be5bbe4ef1f09eb356d6e1c36a7fc468a440b4aa4a116b42e72b8059db536aef50d3a934dbe287e3eb354f90456074d60ff6b4482f67007f830c43eeab504d36cdcf71fbb67b96faed0ff65fa5db942ceb517c27277bda07a6185f7478c15d30cbda8f8b61af8cdc73433699f652ecade973d2a6b700616bb700e2edf32bdb4fcefaa5428c534c75c76a6c79c0a593bb4750f9104b08fe0aff6ecc999560e36a679f7c7cce7fadc6179e6abce2afaae19ad48bddb8f6f0e1b37bc75196d8309d6c5601f4c74f6cd8d39f562fab6bfe31c4e673613f30ec6c1d4669a1a43f816d61a358599ebfd9af66a241eb46749786ad8fcc37edd1fd152d134b512c134a7560e4de30ade380d4df16ba91753743c3134456eeff014f82e4bf9ac8e95274c25b052613dae3a7c97a99d8b2a321365b4a48723083d935de3d7de73091dc1d019c381f384fab9808229f6ce82dbec09e00d9aae78b5779ce0d9b336a985c55e0bf6f149b55422367c1739dbc5289b45c2cd92e879447fb148f0dcde95223ec11b4fdf936eba52272514442cc4fc588fd4c5e2190b5856314a84c43bbc2ea26c95d8a474c2cae313898d6f215c124fcf2c5813befe0e5e760a03efe0a12f51f906de07eaef12e9f9cc5281603dae14cdd3e4c7b3181dcb3ed7e57f4c1e3f8fe07b583c37ab94818e6695d2b36046b9f18cfac0d90d274f86be93c4736ef4ec6fe258be9d54dc238b84d7ceb63230ebffc7313c8b67b6e1b7265ca76c2211f2d2257635328ea813e2417686f788ef138fd38be76a88e335a1575edfc3549d71316a4f2439b2b39678da22c18f02bfaae674af6c8ec7ef1e789bab8d38e2a4deaa263743cf1cb8ebe89908ab25d01b2d4612df24f11ae44dbf5f4912be93218bc504cf6aab91f1c4239e86bc0e8d8630581033ce09181ee98b68c47390fcb0b3745207abb4d545e1d9737a3e22cf1b89ef6ed5d3ad8c1beaa43afee4f3383c903747b0ef459f7112f9a91e5235ae8c7027385275a681603d6ccb9710ef973012377e19acda7982d89e5a02bc33a4eaec69e26d037e3016e6b09e60f4d956e2790af8bbcf125b5b0fa33e154ba277b711ef098883cf9bbae9f3a4b26050167b829eb63606fb77b439403c3d2740576da4ea53814992e079cf2482df2c447f3a81898e14195ba08d789683e52c9a0456d90c71fae9370d554db3e833af78959681df43a04eabdc498cada8899e77887e3f8df1e239efa764d017a4f9703153b5733ee3290a101be85d0d4491aa9d4b214e73109f233ffac31c06dbc42b1e0dca45515afdccb9f44c0f3d2b717a3dc8504c6550bb27104f00783b90f79d0c8fdf4b6c421db467b7f6fd91fe5e5a7f0f1da4edd252173e709607c7ee90335bd684b9f47b013ccba3b71b0fe34ec2ef97948ddd3c9edf4a0aff4e861a46b86801cf984c251ee93a3d53a0060263944a188fe20ea20ac512f81f67a3c326125fcc1d7d2a8c1f7ad6a263f7a728d78dcec670773254dec1f1d931f05efd4abcbd537649bc572423657311c17e6cb5405c58cc79cada48128c393cbb619d2c818f29d1b3ac491219a90a11bcbc338ed8c446882f92aa32e8e39587fad59d8d34be57b440bf8674382789e41e8fab89752f41bfe8c454f88deff1793cc3603d2041da1e682389f1558afd984e953244ccc311974ecf0c29802a68435fa5d43ff48c47e83b00b0b5b03aaee3b12ed14b170f9c6153c1a878a40051ddf1588f9168bbd0e6e279bb941db5c4965347f02c8eb5a66ee09b8290fda57936a3fd1658b0df23e5c5ad8ccd89ed7ff06b71a90ecd022b0b161ee2d0b2757b66b609ad300ed3084d9b70107caf8681f4f87d8dec4ee79487a4d1d036bd6023f1ec05f49d6d60f76fe1aca93318ccfb0ad87cda3edb41b7a565a07f0be8b88538a6a413ee36d05f29f4819d3c2b2f1141de3868b75ae2a815e95925d49fd58267f68a4d0549e9090574ceb0f0a00b563b8bd6087a81f1e54ea7bac26d35251cecee41173d6fee27129d37bc58260feb8cc9dbbf3e87542c4cb05aa630d0f7f07b2e0efa1e0b7d8f85be179a1778aa2fa7a0b7573303ba1d8fba3527513ff463aa5fd0153d638663cfe5a7e7dd92a479198e78895303fea0a7d54bacb77aa98dd7f8e983fc69c3f9553b26d0b390784664903715d7c7c08bed1be4cd025e119e01fb6a909786630ebf29b93ec85b8cbc3ec8efda202f07795fe1b9f241de7ae40520bf9e419e1379ed905ff7206f07f2ce427e5d83bcbdc8ab87fcae7845e0d5cde79202c160d58e8368377f8ef688d675df21edaca21ea76a4703c178b27696ad6f907f9a2481b785e7240779ad24a91179cb86c4eb26492ee4c5f60ef2349b0df95d1fe44da0361af2bb36c89b0abc5eccefea206f16f0ae627e81415e1af0d0569eed1ee42d065e333d273cc8cb01de51ccefca204fb3cb90df97833c27f008e6d731c8db813c6cb7f641de5ee4c16a71d91743f482bc66c8eff3217a41de51c8ef12f2e077b1ae1b464e47bd2ebba0e2ee1bd899c17edb78e89bfaad962f8e55d24fe734906da0fd5e3d346003b478276e1caff6109e71d5db49646e18e7358cb34cefbb2e76603c4cf1e219ab6b21fed7c703f28ce30179c6f1803ce378409e713c340e961d8765c77e19e27fbdecc61b94dd7883b21b6f5036f206c68ede0f4fb0a1f6a363801433213eb663a80d87e815e5bb3098df405dba0679a1311b7b6590171a9f673b691f8131a795ad8d4f3c4b36288f362607c700f242e3313406a8dcfa780c8d01e485c663680c202f341e43630079a1f1181a03c80b8dc7d018405e68ec85c600f242632f340690171a7bda18d078a1b1b7fbb341dec098ba38c81b18f31706790363b46d903730465b077907917715f26b19a2abd018f50dea3f646fcf7e34c80bd9dbdd1f7a81c7ed4d27fa1a2048bffb4ac0b38cda3946f0912a71bd017314fa49daf77e7826b223d6ad4a15ac72a1ce143a43192d4d059f2e9f512ef8595ccb79dc7e68a778686777bf0dfa119e4b4e4aff90d0df31e06b0472895221f1da373ebbdf367e2b3b303e713f764a80c5713dc893f05bbafe216196ae9d74ff02cf2cd3efaca41a9837fd9afd89a7f6e7e9a1f667b8ed1159b57b3d8976c398ea2eeaf5b4b6e29a821d6e77427140afdd015f68ee401e3e8b76ef20de6ef0f39dddb19e6e2f19621338b507fce06e09c63bac5d6ec7f1be5beffb659cdadd0d79a6619e1db4dc5fe178db7d55cbbb9b609c68b73394f7182def909d0ba56f20de09f439f6a97ee4e1f368b7c0601cafd039d49ea03c63409e0e2acf742a4fa75ede18e84f5a79c2607954ae37a81d68c738a1bcbd0283639ad3ca0db5512357b5d309cf08837c5d5eddee419b0f2ddbe2e5414f1707cb16f91b965d7308e22df37fbd6c910f95adeb83c738a1e7a1f2691eaf621ea1f1e1e28d325279a650799af57a0c97350e9fc59e1da2a71bcb5a8be5ecfee4067ae20d7afa9a0c43f4240c2bfb562c7b59d3103d09372cfb352cfbec0737d09330bcec745edd297034fd19911ba8ff645aff53a138553b0546dd01fee46981c37e15d21daca139fcc603f9743dd88ab22fbb4cedc2f9bad983632e7dc1d03117b217686742e32f645fa86d21e43edce331f09261ad40e898e7b17c9ea5dfe86c94e89a06bf2d6827c50b908fe5ca8b0556fb8e2709d61466625bdc4dd72df45e05c837646364a1986def88ed463be8a883311a08e0596a51e3efbe12e25735a563ddfaf5f85f0ef2a91dead5e3770cf21ba98fa1c76fd7f812c89484f335ac03cc246571378ea58016e7ece5c138b944b365c85f766990efc63c7be99d0384c40f96e5c23ed385fa011d4cd5f8d0b66e68b32689c3b95d7b76d41a7a8672a30cb8270c6903faf35bf43cb19ff56a738324e0f747d1c20452659e40921ab632a0c7d770de0f3d8f5eeca4f242d9095afa46d4e3189cdbdb89fd7b219e2e673fc83e7d9087fa5b06698fdeaef370dc60f83b8361ccfbe8b45018f26142e56b7ad1f63a944df8dd8144edf51a7294ca2a83aca1b2f07b7790270efb01f6a3c1f44dff427a09e5f8124f887e3dbdff5f4c1fdb01e9277d3d7de05f4cbfec73487f53287d685e0ac583e7efa9f8ddca796d0dae8d3d29fa46f35d282df4f509d89f607cb1daf8da1dc47e07f9ff29946fc837088d59ba4f6bea36294460f1db0145ec0ea7fb3b927edf8c263f47e5ae75d13b39a04f11b5a787442f4d27725180c36f8a657c27d15487be08ad038e532cef4169c27befbbc4f71e2c0adcf4be24211df7be4b7aef7d083fb8c3fcc71b8d63adac467ae707b4f50dcba27eb7663b18594aa07242f90cda08df26ff7533fd9eced547d7f40d12eeb15e72c4f10c07f1314d284f9499e60b72a32ca1fc517eec97b4fee02316c4db3be9f782e324116da6d582f716d410657c37fad8c960df21af009ee41de37b49eac7b01a903a546d0f947c5b1cfa0d9d65700f06e2da713f0ee70ef41b42edd5de31e6bcb791c17d070bda1b2fe489df2da2ddc67dc161dfe5e5409ebfaad3ee50413fec06f7a83886dca3e2e9c17b549a08ea1df71eff57ee5109d977ac03dde710b4fe237322c925dcb0effc549e059f9425b6d2527b688f10bff1c3ef7fd472fc469125e042e1d71445b897d87e2a58258b105fdc69c77c6d8293b41fddfd8132294c7427d1f71cadda9a34bdd53d9fbe7f6c86f1c0e037955c5c6390ee29bfd1d8efbbd0d4ef4ea7ef1d3fd5fa48fa5af071f9f6e6b35bc1e65f741f5c86df54659bf1bb1e41c8c779a33da2785a7bf3eeb7c16402afc1d61e614f688f389a643e817bbe275e549d61f49b35fc2e019e7d4f2b4ba26b36facd79f22d1c7e738ef5c3ba99a7d2efb2ceb9a7d2fa9da5df9ee377e9f8fdb9fe5dbaf6dda308763cc32c9785d1ef8ab5740d9fb6938848f76df86e37ac5d0e8b20ee69f07bf3a2b3727804cb1d2ea5e7011d87c0cf3f04724961a44a8036768693e8b0db30bf71908e554b79c677d3c23ef36d78875479b8ca4710f334bc476ad1a76a5804eb48867c66435bb878f27e20f9d48381d9a7aa9c5d4118bbf7c84238a92a9d42e2133a098ce97bb8e430869b1d461e7449e7e5b03056e679a69d9c1ae1fbaf4498332b1e84df7365f32ce28ea77aff84ee437a24da271c754eb4952ce707bf04d7194bba59a5ab8de5bc4e463555e077bbac037edba480ddbabf95eefbf86e127a91e21e1eae9f310dd72630d19b44625bdfcd7a9a1a5959249cf28989e14e629fca17e4a717108537e16f0ec7f791b213e495314df83de0f5ff4fc950a7c970e47f20c31129406548791864a8159814285f154de4c8ff5006adaf66315c8a48fba1237e3ee1a695c15a7004ce157c4af867f69452bf9d4b3f41427725d0bb14f01e05ecc3896b99501fc7feebb8b38c51788944f35d4147234fbf9d55364bd0e6f41c0ea3ecf49ba2f996a0f2909d49e1e7cd757879523502e3b951172647c371a29409746cf98bc3c8401e5b4117a13c5ef49bc01e70c3e208c5821a4827ca7689be2f083d53e6d9257c1e1dce6b73f47f49a26dcb13e08f1687c95231f155fb7b5056f4e3204f3694ae6acb5741df827c58837605f1fe06e5219ebe0349e103787f50374dfba2ff3aca81f3d25079a2c3af07e9f7f9069e6f81fd1bf2431f529777d3138cbaf926e26880f83b857ed40f9615cdeb3a2af6f3ffd25d14a1776743ea84f5f42d28c676efc57d69907197660f1bcb54b12348e72afc4eb85aea575d22f94d7c602486c7908e31be17a57efabdf72181fa09325741645306514d35441e5d43609eec53fb0363e8f7d7ebdee7aaa4ee6bf41dcef2ab6c95b4e3baf2e15562dd8f6bc5363a4fa4485fb0a1bb4d5260be469920cd57749eccbb0abf77f452bf9c617aacc925266be236933581de17275993e74ad6c41f4ad6d4b1c0cb92709f03dfb15a138f814d041b1ac6b06ad829560e279c03bf318579dd9a780e740ab2ed14f0fbe530d05f18e80fef583381fe24d09f04fa13417f229419aeedd967b0caa66efaeda9bf98a7eb01477a09679dceb05696e1611e0f5a4776d2ef32ad964eedfbe154e08b09ecd072d11751c609220769f15415bedba173e9c873ac2cd5b0caa4565176c5eb71cd3056c289bc6533ace93b59e513585b413a3c9f866139d745301d9e0e56c2bb45357c04de251684796f84327216a3b0699c2fe6f4559413cf1fa01faafdc6749db847d0a385d1d7890dd276039dcb12b4e79670e2cb0e5c97c5638c4aefb35876ce117f8cc172697993babf562f2affb856d330f9e34710357c0b513e904848375c3cc32b0b244d96805fd75b8d248be05fc5748b98af5a86f9be4bf3457ba88a15a2c29e0e572ed48e56c56241e14f8f53ced79a7c4468c1b651850cd6c776b7413f3937b41e4a66801dac43ec695a07900175856b25ab85e1e9fb4bf1a8886390ea6b3694ef8a24f88d34d720d2f27d110d5755579cce8b24fa77b63dd0bea2127e3a5215eda22fe2f4c7aa5843ac53dee595259276ff97e51c4fefb18236047b37923b184e9487712feb146b136aec561664114a38f0335e9045d0077e03ad9d09a883f61a07f94b8a08756da9c5be053ae81e0d724abe9bbadf0bb5aba63ff4fbed8c2a6648cac8d360ff8b255fd4e953e0c333280ff5b3e797800d725e97674d009ffa5010df8fdb5a6b21fdcb2cf68d6f95bdda4fe544f9415fddf47be417fdec37d7e7ec66dc3be6f60e3e4f19f63c761b3cefc47c30ac6cd7f2c2751dde0d669dd249e5a0df44437ba10cb4fcbdb4fc5e474338d513f2713c6aed19fb4787379cdee744c74c3294056b1825bc89702724fa8e16fd7a254a1ce88bd08617b8d92502de85604d3d867d01cf6c5cd5de3f0e791f99aaf9a7d6c437616c1f035fed5df0d9ce89d01f446b3283f647ba32c5de694ec43bd64a6742fbfdd2113f0fda5c0ac73aa06f6a9d1648405fd5fa9a3b01ed28fdfd86345df75fefe00ee2bbd9121eefabd14ef935d4e21880beb11ff3069dd572f1a5a0f796d19877b42b1074cfc63cdaa629f735d33ad1726e0d4ca379ef91a66139a1f79294572bdd3aecfda4b6476ba2dfb263de2fb5d0fd0c9acfe4401cbeb31b90f535690adee1806587e6064daeddd5b4aedb25fa5ec81a9764a5748a7b32a6d5ea2e597cdb25b0e3bb3b6839d543ca991e9884630f75af9d2738db3b443f31aae99430f41ed4028271777f39f81e5abf0f2fb9cca4f998d2587aa754d1d531f45e8246899ebdc1fb9c64c8548bd338899ee549dc46b82451c239c371186cd2ec32b03f65926356a9c0cd16456e162fe0d91d7fb109cf98413eaff2b8dec47526aeb1a1df02b50fdc0d60137618cfe25cc7bb97e83d4b3006b53584f3167a0753b27e372adecb940c7e3fdee3940af316f8fd328f6bd6dd4dd03719eafbd374422cf4ab339aefffce21eafbdf84beffe6d87fc9f7efd8fdded77dff85ef51dfff26f4fd374ffa7fc4f7ef88fd2bfafedf767f54e8bea8c13b1b047a37e895a964bf390edb79fe257d9d7409f26bc43d017c9765adf19bb0bddb3b5e787e0dd1d2e3fd67aa083a2bd375acad936ab43573ec05ba0681b5f437c775eed2e22e6bd5e29eedf9967cf5b8bbcf6b7163bbbe39aea06a71cf9ed5e22eeb749c15e8fd576af06eb00d411eef7fa8fab491447f5844aadef393e84f0324fa2cacb50373702f455247837d7cfa2a53b511d6cbffd9cf447363c891fffa8228cf0738bc274a1d43ef8560bf45de6dbabc4dbabc17f1fedfe865f576451c43e4822b77cbdfbf1254cd738847eaa1f7e2a17e6ddc15ea93daa457c0e7001f3e6a16bd77d38677d6f8fdf4de3bdf3d4d41b97b1c912b93d1a7e19517c7d0f942d9f8054bef5c29450af2edfc8254b95d4c55936baecd24e2fe2f1b3abd4dfd0e9744f7127d1bc7f4db36898c35e12a919d6903efb1be562f3a16d27eaeb7ef9ff5f6fd87a309fd516d6c2aff05bef16b02499a84771f8fee8f6e5a00e53d4d6cfff905ea15ead0c8447f7a92c84f0718b9e83f214e04897eff2a499904cfaf5e95201f1674c3501ff6f90bac3a3a5fabcbe60fb96f964978566fefa37a7bbf47fde3d35ed035de1d14b81bbfd652a36cc4638a64b8832682e722e509a03f93c4da9cb5849badf9d4559149b00e3d43acc54dfa993833f1457ed11fd28b4da825fa5cf54dfa59adebe7b0ae9f77868e35d5058035e2955b61bc69fde4b06c827c9c26a28f3f0ff54970ac359f6dc2b10675e0dd75cbf07ce803caf53a76e04edf81312bea7909bf061d6cd1de01da83a13d216acf7f5f779cde9d3b8beeaffc28f47e8fee435ea8e307ee7a049b6b2b6d80320f80bf857ea1307857a5200ede89e89c407d7d7ad74af3b2bf81bd2478df0abd0f5528be8e719470c1a4cdb1d25b78be07cbf0361232f45ca60df709a7b860ddd3a8dd9d3a9e6755b7c4ca0ff1b0664a20b616179e9d6cf706aeb3d0274c479676b3496963b00e53502e8c8b77d4799676b1741e68aa633d813ad6babf45bb6bf340d7f079419fb3607dab8d5d3c2724168b38c7d176db7fb656d35ff1f521ef37687de85e8a763f48fad0bd149a8f50a3a56f1ef30ad8cb0c47924ba0a78753033c5df356483ca82083f3ba38393710617d0bef31c3fdbcc620de2b86bf7d93a47e78ced0f28b605df3967f605f0ff3c3bb64f6394be633f399b4352473d4dcb12569da5d2778cb8aa6e9dc573629a9abb6fd69a31bef53b1dcf2cc5a6dd6cdfde5e40ef3147a0fed3e5ba9cb1efabe695469c9bc39e5e71e516eda4ef0fba5d07724e0974ef4b8123927ff67b7c3eb0a6e745fabd7be09d072c9ae7503554b5bf6966cc5fb449e2b65162ae65dacf6b4f455ed29ff6ac9d676d23c9e3ebd7917197a5f484ca610760bbfd17dd11bfa75ad5eab43bcbb5299e6801ac796fb5549b2800e2755a66f58b061b132ef02af8473ac1c9544e6545a934f46385e936608f35322db88e7c249f094cc33acc91f5acae72be6f7f1aedf198b9fb7ee6b4cc01be6c2a4d1d2f6c6dfbbb7faf03bafc7d6eafaaab8bfcbf13b8968b710e08d047833c14f5af66d9ebe59b8973b501ecb9ce4e7f525cabb12c9e45dca3f7972e33b1094b07068a3b05b14673ad3979835f1a3fa98798ed9614c5f92b2d3cdf2c7bbcf70f1fc8c94c82ae2f84e2959fb02131fd6be7167d43c87b73cd67773d1359fd8d5bbb0de9a1c463eaa079f6e866356d8d8ce174ae24bfc4b9c1b779eab9f0effbf53aff55d8476434c491a331ffa437a49ba6aaae0d03f9b568a5e7f0a5f6c87d6b790631b1e8a066f3d743a7c68fc5302c6571e158876ca5c6cd1521d8d21c7981aa6064ffb8b9fbf513af9e266e7602a22184b393a114b89736ec6affec663698367f927aebd9d94f5b677fcfcabbea58ad84d66a6452f3e6d17cadbf2f13612bd0e7acecd5f977f3ce63cd939781a7e68fc8eafc71f3b3c3edecfa3f7c367ab33b197e29d37cfb53369213da8a6cc517a2e3fc1bbfbc828cc0972593cec7e1e1841d63fd43db52913d32dda11926020ed8004240c25a8cedcb078ce2e5fec94aff0ed917586ab708e33ad7eb8f48437a6b50bc3a5afceb2467493ea1ceb58f87fe1a3c73e5968c5afab7e609dd04d3ef98135127eafb59ae1ff67622ebe95957ab1fab1d4d6b772623e7f6b554cf75beb63fa34b9c1df7e7c93b2b198ff5530781fa018d00178f8d7c1e001c0b3fb83c152c0ab80470109bf0d066f053a376f65eeea9c6c4bceba756bd6dd124e7e3c2fed9ec5e98f5956aeb6cccfcb2a5861f9c19aec1ca841884f99c8bb13a2ac2cb4accf5a5708bf0a73d6adcf2fbc71bcbc356bf2e1490561002c808b2584bf7b3f11002240029800618070c008c04888330a301a300610412ab8b140c7417a19e878a0130091f0db0c888aad2013013701a20131809b019300b1000be016c06480153005301510078807dc0ab80d300df01dc0ed80ef02a603be074800cc00cc04d80089805980d980244032e00ec09d8014402ae02ec01cc0dd003bc83a17e4bc07e8f781ce039a06743ed4351db000b010b008702fe03ec00f00f7439cff0064007e087800f4f020d087e0d962c863096029841f063c02f811847f0c7814f018e027c05b06c80464011e0728806c400e3c5f0ec805ac00ac04de1380270179805580d58035807c78be16b00e50002804de7ac0538022c0d3d0c2cf007e0af819e059c006c07f46ec223f0714034a001b010e4029a47102ca002ec86f13a01cb019b005b015f01ce079c00b00fcb70df062c22ea202b6c3ef1d809d0037a012b00bf27a095005a806fc02f05f80dd801ac0cb803d10ef15c0ab80bd805a401df05f03bc0ed807f825e057805f03de00fc06b01fe2bd09f82dc00338003808fc43002fe030e07780df038e00de02bc0df803a01e7014700cf047c09f00c7010d80138046c049c03b803f03fe02f82be06f80ff06fc1d700af02ee03dc0fb800f00a74186338026c087808f40431fc7ee22ff80df9f00fe09f814dae02cd066887f0e701ee003b4005a016d003fe002e022e033c025c065c0e7802f00ed800e4027e04bc0154017a01bd0030800ae02ae01ae03be02f402fa00fd80208060d7d5fe1ed4c8940c3dfc10fef7f47bc16005e035c05b80538016c055c0c8f783410b2011b000f028201fb01df02bc051c007800b805e40c407c1e0144032e03e4026a010b009500dd80f380ef81070197000f009809c867480544006201bf034602ba0067018f0574033a00bf0f01948f36130381e9007bf4d8062c076c0af0047011f002e007a01114d903fa01a900cb80f90092804ec071c076c027c08b80c88813489800ac8ff00e0af80cb00d34760bb010b00d98067019580a3804f005701e33f867a008aff01361ff016e0610c035e051c055c00f0c0b7005201d97adc038093805380984f4006c0b3801ac061c07d9f68cf4e01cdd67fe3f315ff040aa8007c08b8003c1ee814c0ab800380e3ffd4e23e3b244fa4519f827c804a4032c479f89f83cf6bf43242145101f10a014f038a019b00d88fd6af5e91b53a3b0f66343a29e9b312d8bc35eb722cd9eb57e5df69995a104ec8f7b3d617e458129e9e9a306376ded3b75bd232be3f1082c7645d4eee8fa726cccc7eecd6a905b759ee1a12f39b9e84d372967fd3e35ba72ebfed76cbb73ec61c4c8b4831b9119df90d7c9dda677cfbf32949dffebc79b5462fe8f4aa4ec91a8d46e8344aa7b7ea3441a7769d2ed0e9c33acdd4e9d38670b121bc49a7153addafd3c33a3da9d3533abd6a08c304a5d547a7b71ac2098670b2216c37841718c2193a7d58a72b0ce17c43f86943b8d810de64085718c29586708d21fc9a21bc3f7fb83e0f1bc2470de1938670c62a3d3f9dae3084f30de1a70de162437893215c6108571ac2353a7d4da7470de1933a3da5d30e43f8aa4e89de6fa3560f0f5b0ce15b0de1044338d910b6eb74814e330de1158670be21fcb4215c6c086f32842b0ce14a43b8c6107e4da7fb757ad2103e65087f68082f2f4cb0209d9f6129cc5995bf665dd6ba953905c89f61d19ecfd4a94da7893a9da5d3d93a4dd268c1407e05594f813906c3b7b2002c7201d52a3cd7f3cd1a8897b52e77fdaa9cd58505df5b9753b87edd6acb535979eb7342f1b342f1677e2dfed088cbb374f9b274f9b274f9b274f9b242f20de4f3e08de5d3f329d0f329d0f329d0f32908e593acd33b743a2341a733347d2487ca796850af96ef67e5e5e5aca3cff574857aba422ddd4f73d6adc1070bb2d6657fb768e53a901059645d96c582fc073415656567afcb2900990bf235fe838559ca9396fc3574f68370aece4fcf5bf37856dee083429dffd08a753959d943f8093aff46c2c21f7407fa1cba03a50509df5b8eabbbe14afcdef27559ab728688410af474c3e3c19a442f6ffefad54ae1ca35abbfbd17407c3d9fac99df946e7802e80e5afc449dced2e96c9d26e9f5987923f90a48819ebe404f5fa0a72fd0d31784d227ebf40e9dce48d0e90c4d5f7a3e857a3e857a3e857a3e2b966ae370bb4e8feaf4b24ea31ed6c7fb231a2dd4e98a1fe9e1c5df4e1f87667e92b687a697e559ebf30a2dcb730a951543c2796bb2b2873e2f2804b78986574217c8850eb4727541e1baf554e594bf6a654196be853098d9703ecd7448783053f26fbfe3df7ec7703fe0df7ec7f0f0ffe97e47f48f13526c3356a5596e9d9ab7fe36589bdd69f98ff5859635cb2dab7256ad59f74c384458050ba3071f79f0fb73efbb4f8b6f5bb57448fc82670a96e53cbdb2d0a2e03c956d79fc190b5ded4dcdcbb614ad2c5c816b2c589e6919dda0bcc5ab0bd6e7c3645788b6e99902cc05d3de69c94ab86b2aaef8b266e87426d25b4222fdeffd891ae10d6c61688065ff774b19fc93fe2791b97f39e6767dfec9d6e7995bf5f9ea039d7ffb8f75fea31acdd3e36dd5f9037f1bf47ea1d3ccff343cfffff91fa3d3d1b6bffd3271dcaab6cb9f0a64c4ffab12fdfbefdf7ffffefbf7dfbffffe4ffc8bf8b9367f86a8c540130cd46ea019069a69a0f9065a6ca015065a63a0fb0df4a8819e32d06603ed3050f87f188d30508b812618a8dd40330c34d340f30db4d8402b0cb4c640f71be851033d65a0cd06da61a0a464388d30508b812618a8dd40330c34d340f30db4d8402b0cb4c640f71be851033d65a0cd06da61a064e3701a61a016034d3050bb81661868a681e61b68b1815618688d81ee37d0a3067aca409b0db4c3408963388d30508b812618a8dd40330c34d340f30db4d8402b0cb4c640f71be851033d65a0cd06da61a0a474388d30508b812618a8dd40330c34d340f30db4d8402b0cb4c640f71be851033d65a0cd06da61a0c4399c4618a8c540130cd46ea019069a69a0f9065a6ca015065a63a0fb0df4a8819e32d06603ed305052369c4618a8c540130cd46ea019069a69a0f9065a6ca015065a63a0fb0df4a8819e32d06603ed3050e23250fd2f18d4de8b9f2cd2f8211ad4ff0819d863d07e3003e1642a4f053f2cbfd0dfff05896fca73'

ISP_PROG = binascii.unhexlify(ISP_PROG)

#print('ISP_FLASH progam size (compressed)', len(ISP_PROG))
ISP_PROG = zlib.decompress(ISP_PROG)
#print('ISP_FLASH progam size (decompressed)', len(ISP_PROG))

def printProgressBar (iteration, total, prefix = '', suffix = '', decimals = 1, length = 100, fill = '█'):
    """
    Call in a loop to create terminal progress bar
    @params:
        iteration   - Required  : current iteration (Int)
        total       - Required  : total iterations (Int)
        prefix      - Optional  : prefix string (Str)
        suffix      - Optional  : suffix string (Str)
        decimals    - Optional  : positive number of decimals in percent complete (Int)
        length      - Optional  : character length of bar (Int)
        fill        - Optional  : bar fill character (Str)
    """
    percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
    filledLength = int(length * iteration // total)
    bar = fill * filledLength + '-' * (length - filledLength)
    print('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix), end = '\r')
    # Print New Line on Complete
    if iteration == total:
        print()

def slip_reader(port):
    partial_packet = None
    in_escape = False

    while True:
        waiting = port.inWaiting()
        read_bytes = port.read(1 if waiting == 0 else waiting)
        if read_bytes == b'':
            raise Exception("Timed out waiting for packet %s" % ("header" if partial_packet is None else "content"))
        for b in read_bytes:

            if type(b) is int:
                b = bytes([b])  # python 2/3 compat

            if partial_packet is None:  # waiting for packet header
                if b == b'\xc0':
                    partial_packet = b""
                else:
                    raise Exception('Invalid head of packet (%r)' % b)
            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if b == b'\xdc':
                    partial_packet += b'\xc0'
                elif b == b'\xdd':
                    partial_packet += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', b))
            elif b == b'\xdb':  # start of escape sequence
                in_escape = True
            elif b == b'\xc0':  # end of packet
                yield partial_packet
                partial_packet = None
            else:  # normal byte in packet
                partial_packet += b


class ISPResponse:
    class ISPOperation(Enum):
        ISP_ECHO = 0xC1
        ISP_NOP = 0xC2
        ISP_MEMORY_WRITE = 0xC3
        ISP_MEMORY_READ = 0xC4
        ISP_MEMORY_BOOT = 0xC5
        ISP_DEBUG_INFO = 0xD1

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        try:
            if ISPResponse.ISPOperation(op) == ISPResponse.ISPOperation.ISP_DEBUG_INFO:
                text = data[2:].decode()
        except ValueError:
            print('Warning: recv unknown op', op)

        return (op, reason, text)


class FlashModeResponse:
    class Operation(Enum):
        ISP_DEBUG_INFO = 0xD1
        ISP_NOP = 0xD2
        ISP_FLASH_ERASE = 0xD3
        ISP_FLASH_WRITE = 0xD4
        ISP_REBOOT = 0xD5
        ISP_UARTHS_BAUDRATE_SET = 0xD6
        FLASHMODE_FLASH_INIT = 0xD7

    class ErrorCode(Enum):
        ISP_RET_DEFAULT = 0
        ISP_RET_OK = 0xE0
        ISP_RET_BAD_DATA_LEN = 0xE1
        ISP_RET_BAD_DATA_CHECKSUM = 0xE2
        ISP_RET_INVALID_COMMAND = 0xE3

    @staticmethod
    def parse(data):
        op = data[0]
        reason = data[1]
        text = ''
        if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_DEBUG_INFO:
            text = data[2:].decode()

        return (op, reason, text)


def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]


class MAIXLoader:
    def change_baudrate(self, baudrate):
        print(INFO_MSG,"Selected Baudrate: ", baudrate, BASH_TIPS['DEFAULT'])
        out = struct.pack('III', 0, 4, baudrate)
        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)
        out = struct.pack('HH', 0xd6, 0x00) + crc32_checksum + out
        self.write(out)
        time.sleep(0.05)
        self._port.baudrate = baudrate

    def __init__(self, port='/dev/ttyUSB1', baudrate=115200):
        # configure the serial connections (the parameters differs on the device you are connecting to)
        self._port = serial.Serial(
            port=port,
            baudrate=baudrate,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=0.1
        )
        print(INFO_MSG, "Default baudrate is", baudrate, ", later it may be changed to the value you set.",  BASH_TIPS['DEFAULT'])

        self._port.isOpen()
        self._slip_reader = slip_reader(self._port)

    """ Read a SLIP packet from the serial port """

    def read(self):
        return next(self._slip_reader)

    """ Write bytes to the serial port while performing SLIP escaping """

    def write(self, packet):
        buf = b'\xc0' \
              + (packet.replace(b'\xdb', b'\xdb\xdd').replace(b'\xc0', b'\xdb\xdc')) \
              + b'\xc0'
        #print('[WRITE]', binascii.hexlify(buf))
        return self._port.write(buf)

    def read_loop(self):
        out = b''
        # while self._port.inWaiting() > 0:
        #     out += self._port.read(1)

        # print(out)
        while 1:
            sys.stdout.write('[RECV] raw data: ')
            sys.stdout.write(binascii.hexlify(self._port.read(1)).decode())
            sys.stdout.flush()

    def recv_one_return(self):
        timeout_init = time.time()
        data = b''
        # find start boarder
        #sys.stdout.write('[RECV one return] raw data: ')
        while 1:
            if time.time() - timeout_init > timeout:
                raise TimeoutError
            c = self._port.read(1)
            #sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

        in_escape = False
        while 1:
            if time.time() - timeout_init > timeout:
                raise TimeoutError
            c = self._port.read(1)
            #sys.stdout.write(binascii.hexlify(c).decode())
            sys.stdout.flush()
            if c == b'\xc0':
                break

            elif in_escape:  # part-way through escape sequence
                in_escape = False
                if c == b'\xdc':
                    data += b'\xc0'
                elif c == b'\xdd':
                    data += b'\xdb'
                else:
                    raise Exception('Invalid SLIP escape (%r%r)' % (b'\xdb', b))
            elif c == b'\xdb':  # start of escape sequence
                in_escape = True

            data += c

        #sys.stdout.write('\n')
        return data

    def reset_to_isp_kd233(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.01)
        #print('-- RESET to LOW, IO16 to HIGH --')
        # Pull reset down and keep 10ms
        self._port.setDTR (True)
        self._port.setRTS (False)
        time.sleep(0.01)
        #print('-- IO16 to LOW, RESET to HIGH --')
        # Pull IO16 to low and release reset
        self._port.setRTS (True)
        self._port.setDTR (False)
        time.sleep(0.01)

    def reset_to_isp_dan(self):
        self._port.dtr = False
        self._port.rts = False
        time.sleep(0.01)
        #print('-- RESET to LOW, IO16 to HIGH --')
        # Pull reset down and keep 10ms
        self._port.dtr = False
        self._port.rts = True
        time.sleep(0.01)
        #print('-- IO16 to LOW, RESET to HIGH --')
        # Pull IO16 to low and release reset
        self._port.rts = False
        self._port.dtr = True
        time.sleep(0.01)

    def reset_to_boot(self):
        self._port.setDTR (False)
        self._port.setRTS (False)
        time.sleep(0.01)
        #print('-- RESET to LOW --')
        # Pull reset down and keep 10ms
        self._port.setRTS (False)
        self._port.setDTR (True)
        time.sleep(0.01)
        #print('-- RESET to HIGH, BOOT --')
        # Pull IO16 to low and release reset
        self._port.setRTS (False)
        self._port.setDTR (False)
        time.sleep(0.01)

    def greeting(self):
        self._port.write(b'\xc0\xc2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = ISPResponse.parse(self.recv_one_return())

        #print('MAIX return op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)


    def flash_greeting(self):
        retry_count = 0
        while 1:
            self._port.write(b'\xc0\xd2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
            retry_count = retry_count + 1
            try:
                op, reason, text = FlashModeResponse.parse(self.recv_one_return())
            except IndexError:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                time.sleep(0.1)
                continue
                print(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT'])
            #print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
            #      FlashModeResponse.ErrorCode(reason).name)
            if FlashModeResponse.Operation(op) == FlashModeResponse.Operation.ISP_NOP:
                print(INFO_MSG,"Boot to Flashmode Successfully",BASH_TIPS['DEFAULT'])
                break
            else:
                if retry_count > MAX_RETRY_TIMES:
                    print(ERROR_MSG,"Failed to Connect to K210's Stub",BASH_TIPS['DEFAULT'])
                    sys.exit(1)
                print(WARN_MSG,"Unexcepted Return recevied, retrying...",BASH_TIPS['DEFAULT'])
                time.sleep(0.1)
                continue

    def boot(self, address=0x80000000):
        print(INFO_MSG,"Booting From " + hex(address),BASH_TIPS['DEFAULT'])

        out = struct.pack('II', address, 0)

        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)

        out = struct.pack('HH', 0xc5, 0x00) + crc32_checksum + out  # op: ISP_MEMORY_WRITE: 0xc3
        self.write(out)

    def recv_debug(self):
        op, reason, text = ISPResponse.parse(self.recv_one_return())
        #print('[RECV] op:', ISPResponse.ISPOperation(op).name, 'reason:', ISPResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)
        if ISPResponse.ErrorCode(reason) not in (ISPResponse.ErrorCode.ISP_RET_DEFAULT, ISPResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry, errcode=', hex(reason))
            return False
        return True

    def flash_recv_debug(self):
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        #print('[Flash-RECV] op:', FlashModeResponse.Operation(op).name, 'reason:',
        #      FlashModeResponse.ErrorCode(reason).name)
        if text:
            print('-' * 30)
            print(text)
            print('-' * 30)

        if FlashModeResponse.ErrorCode(reason) not in (FlashModeResponse.ErrorCode.ISP_RET_OK, FlashModeResponse.ErrorCode.ISP_RET_OK):
            print('Failed, retry')
            return False
        return True

    def init_flash(self, chip_type):
        chip_type = int(chip_type)
        print(INFO_MSG,"Selected Flash: ",("In-Chip", "On-Board")[chip_type],BASH_TIPS['DEFAULT'])
        out = struct.pack('II', chip_type, 0)
        crc32_checksum = struct.pack('I', binascii.crc32(out) & 0xFFFFFFFF)

        out = struct.pack('HH', 0xd7, 0x00) + crc32_checksum + out

        sent = self.write(out)
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        #print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
        #      FlashModeResponse.ErrorCode(reason).name)

    def flash_dataframe(self, data, address=0x80000000):
        DATAFRAME_SIZE = 1024
        data_chunks = chunks(data, DATAFRAME_SIZE)
        #print('[DEBUG] flash dataframe | data length:', len(data))
        total_chunk = math.ceil(len(data)/DATAFRAME_SIZE)

        for n, chunk in enumerate(data_chunks):
            while 1:
                #print('[INFO] sending chunk', i, '@address', hex(address), 'chunklen', len(chunk))
                out = struct.pack('II', address, len(chunk))

                crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

                out = struct.pack('HH', 0xc3, 0x00) + crc32_checksum + out + chunk  # op: ISP_MEMORY_WRITE: 0xc3
                sent = self.write(out)
                #print('[INFO]', 'sent', sent, 'bytes', 'checksum', binascii.hexlify(crc32_checksum).decode())

                address += len(chunk)

                if self.recv_debug():
                    break
            printProgressBar(n+1, total_chunk, prefix = 'Downloading ISP:', suffix = 'Complete', length = 50)

    def dump_to_flash(self, data, address=0):
        '''
        typedef struct __attribute__((packed)) {
            uint8_t op;
            int32_t checksum; // 下面的所有字段都要参与checksum的计算
            uint32_t address;
            uint32_t data_len;
            uint8_t data_buf[1024];
        } isp_request_t;
        '''

        DATAFRAME_SIZE = 4096
        data_chunks = chunks(data, DATAFRAME_SIZE)
        #print('[DEBUG] flash dataframe | data length:', len(data))



        for n, chunk in enumerate(data_chunks):
            #print('[INFO] sending chunk', i, '@address', hex(address))
            out = struct.pack('II', address, len(chunk))

            crc32_checksum = struct.pack('I', binascii.crc32(out + chunk) & 0xFFFFFFFF)

            out = struct.pack('HH', 0xd4, 0x00) + crc32_checksum + out + chunk
            #print("[$$$$]", binascii.hexlify(out[:32]).decode())
            retry_count = 0
            while True:
                try:
                    sent = self.write(out)
                    #print('[INFO]', 'sent', sent, 'bytes', 'checksum', crc32_checksum)
                    self.flash_recv_debug()
                except:
                    retry_count = retry_count + 1
                    if retry_count > MAX_RETRY_TIMES:
                        print(ERROR_MSG,"Error Count Exceeded, Stop Trying",BASH_TIPS['DEFAULT'])
                        sys.exit(1)
                    continue
                break
            address += len(chunk)



    def flash_erase(self):
        #print('[DEBUG] erasing spi flash.')
        self._port.write(b'\xc0\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0')
        op, reason, text = FlashModeResponse.parse(self.recv_one_return())
        #print('MAIX return op:', FlashModeResponse.Operation(op).name, 'reason:',
        #      FlashModeResponse.ErrorCode(reason).name)

    def install_flash_bootloader(self, data):
        # 1. 刷入 flash bootloader
        self.flash_dataframe(data, address=0x80000000)

    def flash_firmware(self, firmware_bin: bytes, aes_key: bytes = None, address_offset = 0, sha256Prefix = True):
        #print('[DEBUG] flash_firmware DEBUG: aeskey=', aes_key)

        if sha256Prefix == True:
            # 固件加上头
            # 格式: SHA256(after)(32bytes) + AES_CIPHER_FLAG (1byte) + firmware_size(4bytes) + firmware_data
            aes_cipher_flag = b'\x01' if aes_key else b'\x00'

            # 加密
            if aes_key:
                enc = AES_128_CBC(aes_key, iv=b'\x00'*16).encrypt
                padded = firmware_bin + b'\x00'*15 # zero pad
                firmware_bin = b''.join([enc(padded[i*16:i*16+16]) for i in range(len(padded)//16)])

            firmware_len = len(firmware_bin)

            data = aes_cipher_flag + struct.pack('I', firmware_len) + firmware_bin

            sha256_hash = hashlib.sha256(data).digest()

            firmware_with_header = data + sha256_hash

            total_chunk = math.ceil(len(firmware_with_header)/4096)
            # 3. 分片刷入固件
            data_chunks = chunks(firmware_with_header, 4096)  # 4kb for a sector
        else:
            total_chunk = math.ceil(len(firmware_bin)/4096)
            data_chunks = chunks(firmware_bin, 4096)

        for n, chunk in enumerate(data_chunks):
            chunk = chunk.ljust(4096, b'\x00')  # align by 4kb

            # 3.1 刷入一个dataframe
            #print('[INFO]', 'Write firmware data piece')
            self.dump_to_flash(chunk, address= n * 4096 + address_offset)
            printProgressBar(n+1, total_chunk, prefix = 'Downloading:', suffix = 'Complete', length = 50)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--port", help="COM Port", default="DEFAULT")
    parser.add_argument("-c", "--chip", help="SPI Flash type, 1 for in-chip, 0 for on-board", default=1)
    parser.add_argument("-b", "--baudrate", type=int, help="UART baudrate for uploading firmware", default=115200)
    parser.add_argument("-l", "--bootloader", help="bootloader bin path", required=False, default=None)
    parser.add_argument("-k", "--key", help="AES key in hex, if you need encrypt your firmware.", required=False, default=None)
    parser.add_argument("-v", "--verbose", help="increase output verbosity", default=False,
                        action="store_true")
    parser.add_argument("-t", "--terminal", help="Start a terminal after finish", default=False, action="store_true")
    parser.add_argument("firmware", help="firmware bin path")

    args = parser.parse_args()
    if args.port == "DEFAULT":
        try:
            list_port_info = next(serial.tools.list_ports.grep(VID_LIST_FOR_AUTO_LOOKUP)) #Take the first one within the list
            print(INFO_MSG,"COM Port Auto Detected, Selected ",list_port_info.device,BASH_TIPS['DEFAULT'])
            _port = list_port_info.device
        except StopIteration:
            print(ERROR_MSG,"No vaild COM Port found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`--port/-p`',BASH_TIPS['DEFAULT'])
            sys.exit(1)
    else:
        _port = args.port
        print(INFO_MSG,"COM Port Selected Manually: ",_port,BASH_TIPS['DEFAULT'])

    loader = MAIXLoader(port=_port, baudrate=115200)


    # 1. Greeting.
    print(INFO_MSG,"Trying to Enter the ISP Mode...",BASH_TIPS['DEFAULT'])

    retry_count = 0

    while 1:
        retry_count = retry_count + 1
        if retry_count > 15:
            print("\n" + ERROR_MSG,"No vaild Kendryte K210 found in Auto Detect, Check Your Connection or Specify One by"+BASH_TIPS['GREEN']+'`-p '+('/dev/ttyUSB0', 'COM3')[sys.platform == 'win32']+'`',BASH_TIPS['DEFAULT'])
            sys.exit(1)
        try:
            print('.', end='')
            loader.reset_to_isp_dan()
            loader.greeting()
            break
        except TimeoutError:
            pass

        try:
            print('_', end='')
            loader.reset_to_isp_kd233()
            loader.greeting()
            break
        except TimeoutError:
            pass
    timeout = 3
    print()
    print(INFO_MSG,"Greeting Message Detected, Start Downloading ISP",BASH_TIPS['DEFAULT'])
    # 2. flash bootloader and firmware
    try:
        firmware_bin = open(args.firmware, 'rb')
    except FileNotFoundError:
        print(ERROR_MSG,'Unable to find the firmware at ', args.firmware, BASH_TIPS['DEFAULT'])
        sys.exit(1)

    # install bootloader at 0x80000000
    if args.bootloader:
        loader.install_flash_bootloader(open(args.bootloader, 'rb').read())
    else:
        loader.install_flash_bootloader(ISP_PROG)

    loader.boot()

    print(INFO_MSG,"Wait For 0.3 second for ISP to Boot", BASH_TIPS['DEFAULT'])

    time.sleep(0.3)

    loader.flash_greeting()

    if args.baudrate != 115200:
        loader.change_baudrate(args.baudrate)

    loader.init_flash(args.chip)

    if ".kfpkg" == os.path.splitext(args.firmware)[1]:
        print(INFO_MSG,"Extracting KFPKG ... ", BASH_TIPS['DEFAULT'])
        firmware_bin.close()
        with tempfile.TemporaryDirectory() as tmpdir:
            try:
                with zipfile.ZipFile(args.firmware) as zf:
                    zf.extractall(tmpdir)
            except zipfile.BadZipFile:
                print(ERROR_MSG,'Unable to Decompress the kfpkg, your file might be corrupted.',BASH_TIPS['DEFAULT'])
                sys.exit(1)

            fFlashList = open(os.path.join(tmpdir, 'flash-list.json'), "r")
            sFlashList = re.sub(r'"address": (.*),', r'"address": "\1",', fFlashList.read()) #Pack the Hex Number in json into str
            fFlashList.close()
            jsonFlashList = json.loads(sFlashList)
            for lBinFiles in jsonFlashList['files']:
                print(INFO_MSG,"Writing",lBinFiles['bin'],"into","0x%08x"%int(lBinFiles['address'], 0),BASH_TIPS['DEFAULT'])
                firmware_bin = open(os.path.join(tmpdir, lBinFiles["bin"]), "rb")
                loader.flash_firmware(firmware_bin.read(), None, int(lBinFiles['address'], 0), lBinFiles['sha256Prefix'])
                firmware_bin.close()
    else:
        if args.key:
            aes_key = binascii.a2b_hex(args.key)
            if len(aes_key) != 16:
                raise ValueError('AES key must by 16 bytes')

            loader.flash_firmware(firmware_bin.read(), aes_key=aes_key)
        else:
            loader.flash_firmware(firmware_bin.read())

    # 3. boot
    loader.reset_to_boot()
    print(INFO_MSG,"Rebooting...", BASH_TIPS['DEFAULT'])
    loader._port.close()

    if(args.terminal == True):
        import serial.tools.miniterm
        sys.argv = ['']
        serial.tools.miniterm.main(default_port=_port, default_baudrate=115200, default_dtr=False, default_rts=False)
